#pragma once
#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>

template<typename T>
class threadsafe_queue
{
private:
	mutable std::mutex m_mutex;
	std::condition_variable m_con;
	std::queue<T> m_queue;

public:
	threadsafe_queue() {

	}

	threadsafe_queue(threadsafe_queue const& _queue)
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		m_queue = _queue.m_queue;
	}

	void push(T new_value)
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		m_queue.push(new_value);
		m_con.notify_one();
	}

	void wait_and_pop(T& value)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_con.wait(lock, [this] { return !m_queue.empty(); });
		value = m_queue.front();
		m_queue.pop();
	}

	std::shared_ptr<T> wait_and_pop()
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_con.wait(lock, [this] { return !m_queue.empty(); });
		std::shared_ptr<T> res(std::make_shared<T>(m_queue.front()));
		m_queue.pop();
		return res;
	}

	bool try_pop(T& value)
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		if (m_queue.empty())
			return false;
		value = m_queue.front();
		m_queue.pop();
		return true;
	}

	std::shared_ptr<T> try_pop()
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		if (m_queue.empty())
			return std::shared_ptr<T>();
		std::shared_ptr<T> res(std::make_shared<T>(m_queue.front()));
		m_queue.pop();
		return res;
	}

	bool empty() const
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		return m_queue.empty();
	}
};

